/*
 *   Copyright (C) 2007 by Andrzej Oczkowicz
 *   andrew_lz@poczta.fm
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *   KMediaCatalog :)
 *
 */

#include <QUrl>
#include <QTimer>
#include <QFileDialog>
#include <QCompleter>
#include <QDirModel>

#include "kmedia.h"
#include "dbg.h"
#include "kmaster.h"
#include "kutils.h"
#include "ksearch.h"

kMedia::kMedia(QObject *parent)
 : QObject(parent)
{
  prepareCache();
}


kMedia::~kMedia()
{
}

bool kMedia::readFile(const QString & file, kDrives & items)
{
  QFile f( file );
  if ( ! f.open( QIODevice::ReadOnly ) )
    return false;
  QTextStream t( &f );
  QString item;
  QStringList invald = invalidFs();
  while ( !t.atEnd() ){
    item = t.readLine().simplified().trimmed();
    if ( item.startsWith("#") || item.isEmpty() )
      continue;
    kMedia::rawItem ditem;
    ditem.device = item.section(" ", 0, 0).replace("\\040", QString(' '));
    ditem.mountPoint = item.section(" ", 1, 1).replace("\\040", QString(' '));
    ditem.fs = item.section(" ", 2, 2);
    if ( invald.contains(ditem.fs) )
      continue;
    blkDevInfo(ditem.device, ditem.label, ditem.uid);
    items.append(ditem);
  }
  f.close();
  return true;
}

bool kMedia::blkDevInfo(const QString & dev, QString & label, QString & uid)
{
  blkid_cache blkid = 0;
  blkid_get_cache(&blkid, NULL);
  if (!blkid)
    return false;
  label = blkid_get_tag_value(blkid, "LABEL", dev.toAscii() );
  uid   = blkid_get_tag_value(blkid, "UUID", dev.toAscii() );
  blkid_put_cache(blkid);
  return true;
}

QStringList kMedia::invalidFs()
{
  return QStringList() << "procfs" << "proc" << "swap" << "/dev/pts" << "tmpfs" << "nfsd" 
                       << "devpts" << "sysfs" << "rpc_pipefs" << "usbfs" << "securityfs"
                       << "binfmt_misc" << "devfs" << "usbdevfs" << "selinuxfs" << "none";
}

void kMedia::prepareCache()
{
  fstabRaw.clear();
  mtabRaw.clear();
  readFile(fsTab(), fstabRaw);
  readFile(mTab(), mtabRaw);
  QStringList devices;
  for (int i=0; i<mtabRaw.size(); i++)
    if (!devices.contains( mtabRaw[i].device ) ){
      devices << mtabRaw[i].device;
//       _D << mtabRaw[i].device.toAscii();
    }

  for (int i=0; i<fstabRaw.size(); i++)
    if (!devices.contains( fstabRaw[i].device ) ){
      devices << fstabRaw[i].device;
//       _D << fstabRaw[i].device.toAscii();
    }
  cacheInfo.clear();
  for (int i=0; i<devices.size(); i++){
    mediaItem it;
    rawItem * d = itemDefault(devices[i]);
    if (!d)
      continue;
    it.device     = devices[i];
    it.fs         = d->fs;
    it.mountPoint = d->mountPoint;
    it.state      = devState(it.device);
    it.type       = devType(d);
    it.label      = d->label;
    it.uid        = d->uid;
    cacheInfo.append( it );
  }
}

bool kMedia::isMounted(const QString & dev)
{
  if (!isCached())
    return false;
  return itemByDev(dev, mtabRaw) != 0;
}

kMedia::mediaState kMedia::devState(const QString & dev)
{
  rawItem* ft = itemByDev(dev, fstabRaw);
  rawItem* mt = itemByDev(dev, mtabRaw);
  if (mt)
    return ST_MOUNTED;
  if ( ft && !mt )
    return ST_NO_MOUNTED;
  if (!ft && !mt)
    return ST_NODEV;
  return ST_UNKNOWN;
}

kMedia::mediaType kMedia::devType(rawItem*item)
{
  //MT_UNKNOWN, MT_HDD, MT_CDROM, MT_DVD, MT_FLASH, MT_NET, MT_FLOP, MT_ANOTHER
  if ( !item )
    return MT_UNKNOWN;
  if ( item->fs == "smbfs" || item->fs == "nfs" )
    return MT_NET;
  if ( item->fs == "iso9660" || item->fs == "udf" )
    return MT_CDROM; ///@todo: ioctl dla DVD
  return MT_HDD;
}

QString kMedia::devIcon(mediaType t, mediaState s)
{
  QString mounted = ( s == ST_MOUNTED ) ? "mount" : "unmount"; 
  #define CASE(TYPE, ICON) case TYPE: { return QString(":/devices/%1_%2.png").arg(ICON).arg(mounted); break;}
  switch (t){
    CASE(MT_HDD, "hdd")
    CASE(MT_CDROM, "cdrom")
    CASE(MT_DVD, "dvd")
    CASE(MT_FLASH, "usbpendrive")
    CASE(MT_NET, "nfs")
    CASE(MT_FLOP, "floppy")
    default: return ":/devices/blockdevice.png";
  }
}

kMedia::rawItem * kMedia::itemByDev(const QString & dev, kDrives&info)
{
  for (int i=0; i<info.size(); i++){
    if ( info[i].device == dev )
      return &info[i];
  }
  return 0;
}

kMedia::rawItem * kMedia::itemDefault(const QString & dev)
{
  rawItem *i = itemByDev(dev, mtabRaw);
  if (i)
    return i;
  return itemByDev(dev, fstabRaw);
}

const kMedia::mediaItem * kMedia::driveByIdx(int idx)
{
  if (idx >=0 && idx < cacheInfo.size() )
    return &cacheInfo.at(idx);
  return 0;
}

QString kMedia::devTypeName(mediaType t)
{
  #define TYPE(T, DESC) case T: { return DESC; break;}
  switch (t){
    TYPE(MT_HDD, tr("Hard Disk"))
    TYPE(MT_CDROM, tr("CDROM"))
    TYPE(MT_DVD, tr("DVD"))
    TYPE(MT_FLASH, tr("Flash Drive"))
    TYPE(MT_NET, tr("Network Share"))
    TYPE(MT_FLOP, tr("Floppy Disk"))
    default: return tr("Unknown device");
  }
}

/* dkMedia */
dkMedia::dkMedia(kMedia * parent)
:QDialog(kMaster::getInstance()->mainWindowDialog())
{
  media = parent;
  setupUi(this);
  kUtils::makeTopPicture(topFrame, tr("<b>SELECT MEDIA TO SCAN...</b>"), "right_newcat.png");
  labelMediaScan->setText(tr("<b>READING DEVICES INFO</b>"));
  labelMediaScan->update();
  QCompleter * completer = new QCompleter(this);
  QDirModel *dirModel = new QDirModel(completer);
  dirModel->setFilter(QDir::Dirs | QDir::Drives);
  completer->setModel(dirModel);
  editScanDir->setCompleter(completer);
  update();
  QTimer::singleShot(400, this, SLOT(setDrives()) );
}

dkMedia::~dkMedia()
{
}

void dkMedia::setDrives()
{
//   listDevices->hide();
  listDevices->clear();
  listDevices->setFlow(QListView::LeftToRight);
  listDevices->setWrapping(false);
  listDevices->setMovement(QListView::Static);
  kMedia::kDrivesInfo * dev = media->drives();
  QListWidgetItem * item;
  for (int i=0; i<dev->size(); i++){
    item = new QListWidgetItem(listDevices);
    const kMedia::mediaItem *it = &dev->at(i);
    QString lbl = QFileInfo(it->device).fileName() ;
    if ( lbl.isEmpty() )
      lbl = tr("[rootfs]");
    lbl += '\n';
    lbl += it->label;
    item->setText(lbl);
    item->setIcon( QIcon( media->devIcon(it->type, it->state) ) );
    item->setData(Qt::UserRole, i);
    item->setToolTip( QString("%1\n%2").arg(it->label).arg(it->device) );
    item->setSizeHint( QSize(70, 60) );
  }
  labelMediaScan->setText(tr("Scan device:"));
}

int kMedia::newScan()
{
  dlgMedia = new dkMedia(this);
  return dlgMedia->exec();
}

void dkMedia::on_listDevices_itemActivated(QListWidgetItem * item)
{
  if (item)
   _D << item->text().toAscii();
}

void dkMedia::on_listDevices_currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous)
{
  Q_UNUSED(previous)
  labelDevDesc->setText("<br><br>");
  if (current){
//     _D << current->text().toAscii();
    int idx = current->data(Qt::UserRole).value<int>();
    const kMedia::mediaItem*m = media->driveByIdx(idx);
    if (m) {
      QString desc;
      if ( !m->label.isEmpty() )
        desc = QString("<b>%1</b>: ").arg(m->label);
      QString mounted;
      switch (m->state){
        case kMedia::ST_MOUNTED    : { mounted = tr("mounted"); break; }
        case kMedia::ST_NO_MOUNTED : { mounted = tr("not mounted");  break; }
        default: mounted = "";
      }
      desc += QString( tr("<i><b>%1</b>, device: <b>%2</b>, fs: <b>%3</b></i>") ).arg(media->devTypeName(m->type)).arg(m->device).arg(m->fs);
      if ( !mounted.isEmpty()  )
        desc += QString(tr(", <i><b>%1</b></i>")).arg(mounted);
      desc += "<br>";
      if (m->state == kMedia::ST_MOUNTED){
        desc += QString("Mount point: <i><b><a href=\"file://%1\">%1</a></b></i>").arg(m->mountPoint);
      }
      labelDevDesc->setText(desc);
    }
  }
  updateScanButton();
}


void dkMedia::on_spinBoxNum_valueChanged(int i)
{
 // listDevices->setSpacing(i);
 // listDevices->setBatchSize(i);
  _D << i;
}


void dkMedia::on_buttonBrowse_clicked(bool checked)
{
  QString oldDir( editScanDir->text() );
  QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
                                                 oldDir,
                                                 QFileDialog::ShowDirsOnly /*| QFileDialog::DontResolveSymlinks*/);
  if ( ! dir.isEmpty() ){
    editScanDir->setText( dir );
  }
}

void dkMedia::on_buttonCancel_clicked(bool checked)
{
  Q_UNUSED(checked);
  done(0);
}

void dkMedia::on_buttonScan_clicked(bool checked)
{
  Q_UNUSED(checked);
  QString dir;
  switch ( tabSelectSource->currentIndex() ){
    case 0 : {
      QListWidgetItem * item = listDevices->currentItem();
      if (item){
        int idx = item->data(Qt::UserRole).value<int>();
        const kMedia::mediaItem*m = media->driveByIdx(idx);
        if (m) {
          if ( m->state == kMedia::ST_MOUNTED )
            dir = m->mountPoint;
          else
            kUtils::appMsg(tr("Device <b>%1</b> is not mounted!<br>Please mount it before snanning.").arg(m->device), tr("Device error!") );
        }
      }
      break;
    }
    case 1 : {
      dir = editScanDir->text();
      break;
    }
  }
  if ( !dir.isEmpty() ){
    done(1);
    kSearch search;
    search.scanDirectory(dir);
  }
}

void dkMedia::on_tabSelectSource_currentChanged(int index)
{
  Q_UNUSED(index);
  updateScanButton();
}

void dkMedia::on_editScanDir_textChanged(const QString & text)
{
  Q_UNUSED(text);
  updateScanButton();
}

void dkMedia::updateScanButton()
{
  bool ok = (tabSelectSource->currentIndex() == 0 && listDevices->currentItem() != 0) || 
            (tabSelectSource->currentIndex() == 1 && ! editScanDir->text().isEmpty() );
  buttonScan->setEnabled(ok);
}

